// 测量两台机器的网络延迟与时间差。
// 网络延迟也就是往返时间（ round trip time, RTT ）。注意广域网需要设置 TCP_NODELAY 。
// 时间差（ clock offset ）就是两台机器之间的时间差。
#include <muduo/base/Logging.h>
#include <muduo/net/EventLoop.h>
#include <muduo/net/TcpClient.h>
#include <muduo/net/TcpServer.h>
#include <stdio.h>
#include <string.h>

using namespace muduo;
using namespace muduo::net;

const size_t frameLen = 2 * sizeof(int64_t); // 消息长度固定为 16 字节

void 
ServerConnectionCallback(const TcpConnectionPtr &conn) {
	LOG_TRACE << conn->name() << " " << conn->peerAddress().toIpPort() << " -> "
	        << conn->localAddress().toIpPort() << " is "
	        << (conn->connected() ? "UP" : "DOWN");
	if (conn->connected())
		conn->setTcpNoDelay(true);
}

void 
ServerMessageCallback(const TcpConnectionPtr &conn, Buffer *buf, Timestamp receiveTime) {
	int64_t message[2];
	while (buf->readableBytes() >= frameLen) {
		memcpy(message, buf->peek(), frameLen);
		buf->retrieve(frameLen);
		message[1] = receiveTime.microSecondsSinceEpoch();
		conn->send(message, sizeof(message));
	}
}

void 
RunServer(uint16_t port) {
	EventLoop loop;
	TcpServer server(&loop, InetAddress(port), "ClockServer");
	server.setConnectionCallback(&ServerConnectionCallback);
	server.setMessageCallback(&ServerMessageCallback);
	server.start();
	loop.loop();
}

static TcpConnectionPtr clientConnection;

void 
ClientConnectionCallback(const TcpConnectionPtr &conn) {
	LOG_TRACE << conn->localAddress().toIpPort() << " -> "
		<< conn->peerAddress().toIpPort() << " is "
		<< (conn->connected() ? "UP" : "DOWN");
	if (conn->connected()) {
		clientConnection = conn;
		conn->setTcpNoDelay(true);
	} else {
		clientConnection.reset();
	}
}

void
ClientMessageCallback(const TcpConnectionPtr&, Buffer *buf, Timestamp receiveTime) {
	int64_t message[2];
	while (buf->readableBytes() >= frameLen) {
		memcpy(message, buf->peek(), frameLen);
		buf->retrieve(frameLen);
		// client -> server -> client
		int64_t send = message[0]; // 客户端发送数据包时的时间
		int64_t their = message[1]; // 服务端接收数据包时的时间
		int64_t back = receiveTime.microSecondsSinceEpoch(); // 客户端接收返回的数据包的时间
		int64_t mine = (back + send) / 2; // 客户端的时间点，用于与 their 比较
		LOG_INFO << "*** round trip " << back - send << " clock offset " << their - mine << " (micro second)";
	}
}

void
SendMyTime() {
	if (clientConnection) {
		int64_t message[2];
		message[0] = Timestamp::now().microSecondsSinceEpoch();
		clientConnection->send(message, sizeof(message));
	}
}

void 
RunClient(const char *ip, uint16_t port) {
	EventLoop loop;
	TcpClient client(&loop, InetAddress(ip, port), "ClockClient");
	client.enableRetry();
	client.setConnectionCallback(&ClientConnectionCallback);
	client.setMessageCallback(&ClientMessageCallback);
	client.connect();
	loop.runEvery(0.2, &SendMyTime);
	loop.loop();
}

int
main(int argc, char **argv) {
	Logger::setLogLevel(Logger::INFO);
	if (argc > 2) {
		uint16_t port = static_cast<uint16_t>(atoi(argv[2]));
		if (strcmp(argv[1], "-s") == 0) {
			RunServer(port);
		} else {
			RunClient(argv[1], port);
		}
	} else {
		printf("Usage:\n%s -s port\n%s ip port\n", argv[0], argv[0]);
	}
}
